/*
 * @Author: 13177709051 3506463075@qq.com
 * @Date: 2024-11-24 15:43:24
 * @LastEditors: 13177709051 3506463075@qq.com
 * @LastEditTime: 2024-11-24 16:20:40
 * @FilePath: /linux36/Command.hpp
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cstdio>
#include <set>
#include "Log.hpp"
#include "InetAddr.hpp"

class Command
{
public:
    Command()
    {
        // 白名单
        _safe_command.insert("ls");
        _safe_command.insert("touch"); // touch filename
        _safe_command.insert("pwd");
        _safe_command.insert("whoami");
        _safe_command.insert("which"); // which pwd
    }
    ~Command() {}
    // ls;rm -rf /
    bool SafeCheck(const std::string &cmdstr)
    {
        for(auto &cmd : _safe_command)
        {
            if(strncmp(cmd.c_str(), cmdstr.c_str(), cmd.size()) == 0)
            {
                return true;
            }
        }

        return false;
    }
    std::string Excute(const std::string &cmdstr)
    {
        if(!SafeCheck(cmdstr))
        {
            return "unsafe";
        }
        std::string result;
        FILE *fp = popen(cmdstr.c_str(), "r");
        if(fp)
        {
            char line[1024];
            while(fgets(line, sizeof(line), fp))
            {
                result += line;
            }
            return result.empty() ? "success" : result;
        }
        return "execute error";
    }
    void HandlerCommand(int sockfd, InetAddr addr)
    {
        // 我们把他当做一个长服务
        while (true)
        {
            char commandbuf[1024]; // 当做字符串, ls -a -l -> os --> ls -
            ssize_t n = ::recv(sockfd, commandbuf, sizeof(commandbuf) - 1, 0); // TODO
            if (n > 0)
            {
                commandbuf[n] = 0;
                LOG(INFO, "get command from client %s, command: %s\n", addr.AddrStr().c_str(), commandbuf);
                std::string result = Excute(commandbuf);
                ::send(sockfd, result.c_str(), result.size(), 0);
            }
            else if (n == 0)
            {
                LOG(INFO, "client %s quit\n", addr.AddrStr().c_str());
                break;
            }
            else
            {
                LOG(ERROR, "read error: %s\n", addr.AddrStr().c_str());
                break;
            }
        }
    }
private:
    std::set<std::string> _safe_command;
};

// 这个代码实现了一个简单的命令执行器，允许客户端通过网络发送命令并执行，同时对命令进行安全检查，确保执行的是在白名单中的安全命令。
// Command 构造函数
// 构造函数初始化时，构建了一个白名单 safe_command，该白名单包括允许执行的命令。只有这些命令可以在后续的调用中被安全地执行。
// "ls"：列出目录内容。
// "touch"：创建空文件（命令格式如 touch filename）。
// "pwd"：显示当前工作目录。
// "whoami"：显示当前用户。
// "which"：显示命令的完整路径。
// 这些命令一般是无害的，可以允许远程执行。其他的命令（例如 rm -rf /）则会被拒绝。
// SafeCheck 函数
// 这个函数用于检查命令是否安全。它遍历白名单中的每个命令，使用 strncmp 判断是否有命令前缀匹配。如果有匹配的命令前缀（例如 ls -l），则认为这个命令是安全的，返回 true，否则返回 false。
// strncmp 的作用：strncmp 比较两个字符串的前 n 个字符，cmd.size() 是匹配的字符长度，因此如果 cmdstr 以白名单命令的某个前缀开始，它将被认为是一个安全命令
// Excute 函数
// 这个函数用于执行实际的命令。首先，它会调用 SafeCheck 函数来确保命令是安全的。如果命令不在白名单中，返回 "unsafe"。
// 如果命令安全，它通过 popen 调用操作系统的命令行执行命令，并读取命令输出：
// popen(cmdstr.c_str(), "r") 会执行命令，并返回一个文件指针，可以通过它读取命令的标准输出。
// 使用 fgets 一行一行地读取命令输出，直到没有输出为止。
// 最后将结果返回。如果命令有输出，则返回输出内容；如果命令没有输出，则返回 "success"。
// 如果执行命令时发生错误（例如，popen 失败），返回 "execute error"。
// HandlerCommand 函数
// 这个函数处理与客户端的交互。它是一个长连接服务，持续接收和处理客户端发送的命令。
// 工作流程：
// 使用 recv 函数接收来自客户端的命令，存储在 commandbuf 中。recv 会返回接收到的字节数，如果接收到有效数据（n > 0），继续处理。
// 通过 LOG(INFO, ...) 记录从客户端接收到的命令。
// 使用 Excute 函数执行命令并获取执行结果。
// 使用 send 将命令的执行结果返回给客户端。
// 如果接收到的数据为 0（即客户端关闭连接），则记录日志并结束循环。
// 如果发生读取错误（n < 0），记录错误日志并结束循环。
// 这个方法基本上是一个长连接的服务器循环，不断接收和执行命令，直到客户端断开连接或发生错误。
